已經堅持三天,來到第四天了!雖然有寫作速度漸漸加快的趨勢,但前幾天花費的時間有些超出預期,壓縮到其他行程安排,所以今日就安排了一個相對複雜度低的主題來喘息一下,留點時間規劃接下來的內容。
這個「基於自然語言處理的新聞意見提取應用」預計九成佔比的程式碼會採用 Python,剩下的部分則為網頁架設時會用到的 javascript 等等。有別於以往趕時間完成的作業程式碼,我期許自己能在這次的專案程式碼中做到以下幾點:
我希望透過堅持以上幾點,降低後續自己在維護程式碼時的負擔,也使得後需其他人更容閱讀與參與合作,所以這次決定遵守部分 Google 開源專案會使用的 Google Style Guides 的指引,至於為什麼不是也很常見的 PEP 8 – Style Guide for Python Code,也許是做了以後會去這間大公司寫 code 的夢。
什麼是 「Style(風格)」 呢?
「Style(風格)」”涵蓋了很多方面,從「使用駝峰式命名變數(use camelCase for variable names)」到「不使用全局變量(never use global variables)」都包含在其範疇。
「Style guide(風格指南)」又是什麼?
每個主要的開源項目多數都有各己的 Style guide(風格指南):是一套關於如何為該項目撰寫程式馬的規範。當其中的所有程式碼都採用一致的樣式時,在理解大型代碼庫會容易得多。
以上參考 Google Style Guides 的敘述
下面是 Google Python Style Guide 的整理,想看中文的也有翻譯可以參考 Google Python 風格指南 - 中文版。
本段內容大量參考: Google Python Style Guide 與 Google Python 風格指南 - 中文版 的內容。
Google Python Style Guide 主要分為下面兩大部分:
Python 語言使用規範 (Language Rules)
Python 風格規範 (Style Rules)
優點:
pylint
使用
建議使用 Linting Python in Visual Studio Code 設定為 Pylint,並配合 pylintrc(可點擊下載)
可以通過設置一個行註釋來抑制告警,例如:
dict = 'something awful' # Bad Idea... pylint: disable=redefined-builtin
要抑制「變數未使用」告警,可以用 "" 作為變數標識符,或者在變數名稱前加 "unused"(e.g. unused_b, _ =d,e):
def foo(a, unused_b, unused_c, d=None, e=None):
_ = d, e
return a
import
packages 和 module,而非單一 class 或 function優點:
x.Obj
表示 Obj
定義在 module x
中規則:
import x
來導入 package 和 modulefrom x import y
, 其中 x
是 package 前綴, y
是不帶前綴的 module 名稱from x import y as z
,如果兩個要導入的 module 都叫做 z
或者 y
太長了import
每個 module優點:避免 module 名衝突,找尋 package 更容易
範例:
# Reference in code with complete name.
import sound.effects.echo
# Reference in code with just module name (preferred).
from sound.effects import echo
Exception
except:
語句來捕獲所有異常try
的程式碼量。 try
區塊的體積越大,期望之外的異常就越容易觸發。這種情況下,容易隱藏真正的錯誤。finally
子句來執行那些無論 try
區塊中有沒有異常都應該被執行的代碼。這對於清理資源常常很有用,例如關閉文件。優點:
List、Dict 和 Set Comprehensions 以及 generator 表達式提供了一種簡潔有效的方法來創建容器類型(container types)和迭代器(iterators),而無需使用傳統的循環、map()、filter() 或 lambda。
舉例:
# Yes:
result = [mapping_expr for value in iterable if filter_expr]
result = [{'key': value} for value in iterable
if a_long_filter_expression(value)]
result = [complicated_transform(x)
for x in iterable if predicate(x)]
descriptive_name = [
transform({'key': key, 'value': value}, color='black')
for key, value in generate_iterable(some_input)
if complicated_condition_is_met(key, value)
]
result = []
for x in range(10):
for y in range(5):
if x * y > 10:
result.append((x, y))
return {x: complicated_transform(x)
for x in long_generator_function(parameter)
if x is not None}
squares_generator = (x**2 for x in range(10))
unique_names = {user.name for user in users if user is not None}
eat(jelly_bean for jelly_bean in jelly_beans
if jelly_bean.color == 'black')
# No:
result = [complicated_transform(
x, some_argument=x+1)
for x in iterable if predicate(x)]
result = [(x, y) for x in range(10) for y in range(5) if x * y > 10]
return ((x, y, z)
for x in range(5)
for y in range(5)
if x != y
for z in range(5)
if y != z)
#Yes:
for key in adict: ...
if key not in adict: ...
if obj in alist: ...
for line in afile: ...
for k, v in adict.items(): ...
#No:
for key in adict.keys(): ...
if not adict.has_key(key): ...
for line in afile.readlines(): ...
yield
的使用,但我不常碰到。#Yes:
one_line = 'yes' if predicate(value) else 'no'
slightly_split = ('yes' if predicate(value)
else 'no, nein, nyet')
the_longest_ternary_style_that_can_be_done = (
'yes, true, affirmative, confirmed, correct'
if predicate(value)
else 'no, false, negative, nay')
#No:
bad_line_breaking = ('yes' if predicate(value) else
'no')
portion_too_long = ('yes'
if some_long_module.some_long_predicate_function(
really_long_variable_name)
else 'no, false, negative, nay')
#Yes:
def foo(a, b=None):
if b is None:
b = []
#Yes:
def foo(a, b: Optional[Sequence] = None):
if b is None:
b = []
#Yes:
def foo(a, b: Sequence = ()): # Empty tuple OK since tuples are immutable
...
from absl import flags
_FOO = flags.DEFINE_string(...)
#No:
def foo(a, b=[]):
...
#No:
def foo(a, b=time.time()): # The time the module was loaded???
...
#No:
def foo(a, b=_FOO.value): # sys.argv has not yet been parsed...
...
#No:
def foo(a, b: Mapping = {}): # Could still get passed to unchecked code
...
if foo:
rather than if foo != []:
if foo is None:
(or is not None
) to check for a None valueFalse
using ==
. Use if not x:
instead.if seq:
and if not seq:
are preferable to if len(seq):
and if not len(seq):
respectively.None
as 0). You may compare a value which is known to be an integer (and is not the result of len()
) against the integer 0.def get_adder(summand1):
"""Returns a function that adds numbers to a given number."""
def adder(summand2):
return summand1 + summand2
return adder
@staticmethod
且少用 @classmethod
from __future__ imports
優點
你可以根據 PEP-484
使用類型提示(type hints according)註釋 Python 程式碼,並在構建時(build time)使用 pytype
之類的類型檢查工具對程式碼進行類型檢查。
舉例:
def func(a: int) -> list[int]:
#Yes:
if foo:
bar()
while x:
x = bar()
if x and y:
bar()
if not x:
bar()
# For a 1 item tuple the ()s are more visually obvious than the comma.
onesie = (foo,)
return foo
return spam, beans
return (spam, beans)
for (x, y) in dict.items(): ...
#No:
if (x):
bar()
if not(x):
bar()
return (foo)
spam(ham[1], {'eggs': 2}, [])
spam( ham[ 1 ], { 'eggs': 2 }, [ ] )
Python風格規範 中的補充
在計算機科學中, Shebang (也稱為Hashbang)是一個由井號和歎號構成的字符串行(#!), 其出現在文本文件的第一行的前兩個字符. 在文件中存在Shebang的情況下, 類Unix操作系統的程序載入器會分析Shebang後的內容, 將這些內容作為解釋器指令, 並調用該指令, 並將載有Shebang的文件路徑作為該解釋器的參數. 例如, 以指令#!/bin/sh開頭的文件在執行時會實際調用/bin/sh程序
#!先用於幫助內核找到Python解釋器, 但是在導入模塊時, 將會被忽略. 因此只有被直接執行的文件中才有必要加入#!.
fstring
、 %
operator 或 format
取代對 str
使用 +
操作with
:
with open("hello.txt") as hello_file:
for line in hello_file:
print(line)
# TODO(kl@gmail.com): Use a "*" here for string repetition.
# TODO(Zeke) Change this to use relations.
typing
和 collection.abc
# Yes:
from collections.abc import Mapping, Sequence
import os
import sys
from typing import Any, NewType
# No:
import os, sys
# Yes:
if foo: bar(foo)
# No:
if foo: bar(foo)
else: baz(foo)
try: bar(foo)
except ValueError: baz(foo)
try:
bar(foo)
except ValueError: baz(foo)
module_name, package_name, ClassName, method_name, ExceptionName, function_name, GLOBAL_CONSTANT_NAME, global_var_name, instance_var_name, function_parameter_name, local_var_name, query_proper_noun_for_thing, send_acronym_via_https.
def main():
...
if __name__ == '__main__':
main()
由於先前介紹的 Google Style 頗為複雜,我會建議先稍加熟記,使得程式碼撰寫時多半能按照規則,爾後在 git commit 前使用 YAPF 這一個工具來檢查程式碼風格是否正確。
下方內容參考 YAPF 的文檔。
pip install yapf
pipenv install yapf --dev
完整說明
usage: yapf [-h] [-v] [-d | -i | -q] [-r | -l START-END] [-e PATTERN]
[--style STYLE] [--style-help] [--no-local-style] [-p]
[-vv]
[files ...]
Formatter for Python code.
positional arguments:
files reads from stdin when no files are specified.
optional arguments:
-h, --help show this help message and exit
-v, --version show program's version number and exit
-d, --diff print the diff for the fixed source
-i, --in-place make changes to files in place
-q, --quiet output nothing and set return value
-r, --recursive run recursively over directories
-l START-END, --lines START-END
range of lines to reformat, one-based
-e PATTERN, --exclude PATTERN
patterns for files to exclude from formatting
--style STYLE specify formatting style: either a style name (for
example "pep8" or "google"), or the name of a file
with style settings. The default is pep8 unless a
.style.yapf or setup.cfg or pyproject.toml file
located in the same directory as the source or one
of its parent directories (for stdin, the current
directory is used).
--style-help show style settings and exit; this output can be
saved to .style.yapf to make your settings
permanent
--no-local-style don't search for local style definition
-p, --parallel run YAPF in parallel when formatting multiple
files. Requires concurrent.futures in Python 2.X
-vv, --verbose print out file names while processing
使用 Google style 的範例
yapf -i -r --style google
執行前
x = { 'a':37,'b':42,
'c':927}
y = 'hello ''world'
z = 'hello '+'world'
a = 'hello {}'.format('world')
class foo ( object ):
def f (self ):
return 37*-+2
def g(self, x,y=42):
return y
def f ( a ) :
return 37+-+a[42-x : y**3]
執行後
x = {'a': 37, 'b': 42, 'c': 927}
y = 'hello ' 'world'
z = 'hello ' + 'world'
a = 'hello {}'.format('world')
class foo(object):
def f(self):
return 37 * -+2
def g(self, x, y=42):
return y
def f(a):
return 37 + -+a[42 - x:y**3]
寫的有些匆忙,如果文章有錯誤,歡迎指正~